module axi4_delayer(
  input             clock,
  input             reset,

  output reg        in_arready,
  input             in_arvalid,
  input      [3:0]  in_arid,
  input      [31:0] in_araddr,
  input      [7:0]  in_arlen,
  input      [2:0]  in_arsize,
  input      [1:0]  in_arburst,
  input             in_rready,
  output reg        in_rvalid,
  output reg [3:0]  in_rid,
  output reg [31:0] in_rdata,
  output reg [1:0]  in_rresp,
  output reg        in_rlast,
  output reg        in_awready,
  input             in_awvalid,
  input      [3:0]  in_awid,
  input      [31:0] in_awaddr,
  input      [7:0]  in_awlen,
  input      [2:0]  in_awsize,
  input      [1:0]  in_awburst,
  output reg        in_wready,
  input             in_wvalid,
  input      [31:0] in_wdata,
  input      [3:0]  in_wstrb,
  input             in_wlast,
                    in_bready,
  output reg        in_bvalid,
  output reg [3:0]  in_bid,
  output reg [1:0]  in_bresp,

  input             out_arready,
  output reg        out_arvalid,
  output reg [3:0]  out_arid,
  output reg [31:0] out_araddr,
  output reg [7:0]  out_arlen,
  output reg [2:0]  out_arsize,
  output reg [1:0]  out_arburst,
  output reg        out_rready,
  input             out_rvalid,
  input      [3:0]  out_rid,
  input      [31:0] out_rdata,
  input      [1:0]  out_rresp,
  input             out_rlast,
  input             out_awready,
  output reg        out_awvalid,
  output reg [3:0]  out_awid,
  output reg [31:0] out_awaddr,
  output reg [7:0]  out_awlen,
  output reg [2:0]  out_awsize,
  output reg [1:0]  out_awburst,
  input             out_wready,
  output reg        out_wvalid,
  output reg [31:0] out_wdata,
  output reg [3:0]  out_wstrb,
  output reg        out_wlast,
                    out_bready,
  input             out_bvalid,
  input      [3:0]  out_bid,
  input      [1:0]  out_bresp
);

  localparam INCREMENT    = 5402; // r * s = 5.2758 * 1024
  localparam FIFO_LENGTH  = 8;
  localparam FIFO_POINTER = 3;

  localparam READ_IDLE  = 2'b00;
  localparam READ_COUNT = 2'b01;
  localparam READ_FIFO  = 2'b10;  // write delay parameters into fifo

  // state machine for read_fifo
  localparam READ_FIFO_IDLE = 1'b0;
  localparam READ_FIFO_DELY = 1'b1;

  /* Read calibration */
  wire read_sdram;
  wire pos_in_arvalid;

  wire r_ren;   // read enable for rfifo
  wire r_wen;   // write enable for rfifo
  wire r_empty;
  wire r_full;
  wire [95:0] r_rdata;
  wire [95:0] r_wdata;

  reg [1:0] curr_state;
  reg [1:0] next_state;

  reg  read_fifo_curr_state;
  reg  read_fifo_next_state;

  reg  reg_out_rready;

  reg        reg_in_rvalid;
  reg [3:0]  reg_in_rid;    // ignore for now
  reg [31:0] reg_in_rdata;
  reg [1:0]  reg_in_rresp;  // ignore for now
  reg        reg_in_rlast;

  reg reg_rlast;

  reg pre_arvalid;

  reg [63:0] read_counter;
  reg [63:0] read_pre_counter;
  reg [63:0] read_delay;

  assign read_sdram = in_araddr >= 32'ha000_0000 && in_araddr < 32'hc000_0000;
  assign r_ren = read_fifo_curr_state == READ_FIFO_IDLE && read_fifo_next_state == READ_FIFO_DELY;
  assign r_wen = out_rvalid & out_rready;
  assign r_wdata = {out_rdata, (read_counter >> 10) - read_pre_counter - 1};

  axi4_delayer_fifo #(FIFO_LENGTH, 96) u_axi4_delayer_rfifo (
    .clock(clock),
    .reset(reset),
    .ren(r_ren),
    .wen(r_wen),
    .wdata(r_wdata),
    .rdata(r_rdata),
    .empty(r_empty),
    .full(r_full)
  );

  // get posedge of in_arvalid
  assign pos_in_arvalid = ~pre_arvalid & in_arvalid;
  always @(posedge clock or posedge reset) begin
    if (reset) pre_arvalid <= 1'b0;
    else pre_arvalid <= in_arvalid;
  end

  // delay out_rlast by one cycle
  always @(posedge clock or posedge reset) begin
    if (reset) reg_rlast <= 1'b0;
    else reg_rlast <= out_rlast;
  end

  // state machine for delaying reading
  always @(posedge clock or posedge reset) begin
    if (reset) curr_state <= READ_IDLE;
    else curr_state <= next_state;
  end
  always @(*) begin
    case (curr_state)
      READ_IDLE :
        if (pos_in_arvalid) next_state = READ_COUNT;
        else next_state = READ_IDLE;

      READ_COUNT :
        if (out_rvalid & out_rready) next_state = READ_FIFO;
        else next_state = READ_COUNT;

      READ_FIFO :
        if (reg_rlast) next_state = READ_IDLE;
        else next_state = READ_COUNT;

      default : next_state = READ_IDLE;
    endcase
  end

  // count the number of delayed cycles for reading
  always @(posedge clock or posedge reset) begin
    if (reset) begin
      read_counter <= 0;
      read_pre_counter <= 0;
    end
    else begin
      case (curr_state)
        READ_IDLE  : begin
          read_counter <= INCREMENT;
          read_pre_counter <= 1;
        end

        READ_COUNT : begin
          read_counter <= read_counter + INCREMENT;
          read_pre_counter <= read_pre_counter + 1;
        end

        READ_FIFO : begin
          read_counter <= INCREMENT << 1;
          read_pre_counter <= 2;
        end

        default    : begin
          read_counter <= read_counter;
          read_pre_counter <= read_pre_counter;
        end
      endcase
    end
  end

  /* delay for reading */
  always @(posedge clock or posedge reset) begin
    if (reset) read_fifo_curr_state <= READ_FIFO_IDLE;
    else read_fifo_curr_state <= read_fifo_next_state;
  end
  always @(*) begin
    case (read_fifo_curr_state)
      READ_FIFO_IDLE :
        if (in_rvalid & ~in_rready) read_fifo_next_state = READ_FIFO_IDLE;
        else if (~r_empty) read_fifo_next_state = READ_FIFO_DELY;
        else read_fifo_next_state = READ_FIFO_IDLE;

      READ_FIFO_DELY :
        if (read_delay == 3) read_fifo_next_state = READ_FIFO_IDLE;  // read_delay == 3, because in_rvalid lasts for two cycles
        else read_fifo_next_state = READ_FIFO_DELY;
    endcase
  end

  // read_delay
  always @(posedge clock or posedge reset) begin
    if (reset) read_delay <= 0;
    else if (r_ren) read_delay <= r_rdata[63:0];
    else if (read_fifo_curr_state == READ_FIFO_DELY) read_delay <= read_delay - 1;
  end

  // response to in
  always @(posedge clock or posedge reset) begin
    if (reset) reg_in_rdata <= 32'd0;
    else if (r_ren) reg_in_rdata <= r_rdata[95:64];
  end

  always @(posedge clock or posedge reset) begin
    if (reset) reg_in_rvalid <= 1'b0;
    else if (reg_in_rvalid & in_rready) reg_in_rvalid <= 1'b0;
    else if (read_fifo_curr_state == READ_FIFO_DELY && read_fifo_next_state == READ_FIFO_IDLE) reg_in_rvalid <= 1'b1;
  end

  always @(posedge clock or posedge reset) begin
    if (reset) reg_in_rlast <= 1'b0;
    else if (reg_in_rvalid & in_rready) reg_in_rlast <= 1'b0;
    else if (read_fifo_curr_state == READ_FIFO_DELY && read_fifo_next_state == READ_FIFO_IDLE && r_empty) reg_in_rlast <= 1'b1;
  end

  // response to out
  always @(posedge clock or posedge reset) begin
    if (reset) reg_out_rready <= 1'b0;
    else if (reg_out_rready) reg_out_rready <= 1'b0;
    else if (~reg_out_rready & out_rvalid) reg_out_rready <= 1'b1;
  end


  /* Write calibration */
  localparam WRITE_IDLE  = 2'b00;
  localparam WRITE_COUNT = 2'b01;
  localparam WRITE_DELAY = 2'b10;

  wire pos_in_awvalid;
  wire write_sdram;

  reg pre_awvalid;

  reg [63:0] write_counter;
  reg [63:0] write_pre_counter;
  reg [63:0] write_delay;

  reg [1:0] wcstate;
  reg [1:0] wnstate;

  reg       reg_in_bvalid;
  reg [3:0] reg_in_bid;
  reg [1:0] reg_in_bresp;

  reg       reg_out_bready;

  assign write_sdram = in_awaddr >= 32'ha000_0000 && in_awaddr < 32'hc000_0000;
  assign pos_in_awvalid = ~pre_awvalid & in_awvalid;

  always @(posedge clock or posedge reset) begin
    if (reset) pre_awvalid <= 1'b0;
    else pre_awvalid <= in_awvalid;
  end

  // state machine for delaying writing
  always @(posedge clock or posedge reset) begin
    if (reset) wcstate <= WRITE_IDLE;
    else wcstate <= wnstate;
  end
  always @(*) begin
    case (wcstate)
      WRITE_IDLE:
        if (pos_in_awvalid) wnstate = WRITE_COUNT;
        else wnstate = WRITE_IDLE;

      WRITE_COUNT:
        if (out_bvalid & out_bready) wnstate = WRITE_DELAY;
        else wnstate = WRITE_COUNT;

      WRITE_DELAY:
        if (write_delay == 3) wnstate = WRITE_IDLE;
        else wnstate = WRITE_DELAY;

      default:
        wnstate = WRITE_IDLE;
    endcase
  end

  always @(posedge clock or posedge reset) begin
    if (reset) begin
      write_counter <= 0;
      write_pre_counter <= 0;
    end
    else if (wcstate == WRITE_IDLE) begin
      write_counter <= 2*INCREMENT;
      write_pre_counter <= 2;
    end
    else if (wcstate == WRITE_COUNT) begin
      write_counter <= write_counter + INCREMENT;
      write_pre_counter <= write_pre_counter + 1;
    end
  end

  always @(posedge clock or posedge reset) begin
    if (reset) write_delay <= 0;
    else if (wcstate == WRITE_COUNT && wnstate == WRITE_DELAY) write_delay <= (write_counter >> 10) - write_pre_counter;
    else if (wcstate == WRITE_DELAY) write_delay <= write_delay - 1;
  end

  always @(posedge clock or posedge reset) begin
    if (reset) reg_in_bvalid <= 1'b0;
    else if (reg_in_bvalid & in_bready) reg_in_bvalid <= 1'b0;
    else if (wcstate == WRITE_DELAY && wnstate == WRITE_IDLE) reg_in_bvalid <= 1'b1;
  end

  always @(posedge clock or posedge reset) begin
    if (reset) begin
      reg_in_bid <= 0;
      reg_in_bresp <= 0;  // OKAY
    end
    else if (out_bvalid & reg_out_bready) begin
      reg_in_bid <= out_bid;
      reg_in_bresp <= out_bresp;
    end
  end

  always @(posedge clock or posedge reset) begin
    if (reset) reg_out_bready <= 1'b0;
    else if (reg_out_bready & out_bvalid) reg_out_bready  <= 1'b0;
    else if (~reg_out_bready & out_bvalid) reg_out_bready <= 1'b1;
  end

  // allocate ports for reading
  always @(*) begin
    if (read_sdram) begin
      // read address channel
      in_arready  = out_arready;
      out_arvalid = in_arvalid;
      out_arid    = in_arid;
      out_araddr  = in_araddr;
      out_arlen   = in_arlen;
      out_arsize  = in_arsize;
      out_arburst = in_arburst;

      // read data channel
      out_rready  = reg_out_rready;
      in_rvalid   = reg_in_rvalid;
      in_rid      = out_rid;
      in_rdata    = reg_in_rdata;
      in_rresp    = out_rresp;
      in_rlast    = reg_in_rlast;
    end
    else begin
      in_arready = out_arready;
      out_arvalid = in_arvalid;
      out_arid = in_arid;
      out_araddr = in_araddr;
      out_arlen = in_arlen;
      out_arsize = in_arsize;
      out_arburst = in_arburst;
      out_rready = in_rready;
      in_rvalid = out_rvalid;
      in_rid = out_rid;
      in_rdata = out_rdata;
      in_rresp = out_rresp;
      in_rlast = out_rlast;
    end
  end

  // allocate ports for writing
  always @(*) begin
    if (write_sdram) begin
      // write
      in_awready  = out_awready;
      out_awvalid = in_awvalid;
      out_awid    = in_awid;
      out_awaddr  = in_awaddr;
      out_awlen   = in_awlen;
      out_awsize  = in_awsize;
      out_awburst = in_awburst;
      in_wready   = out_wready;
      out_wvalid  = in_wvalid;
      out_wdata   = in_wdata;
      out_wstrb   = in_wstrb;
      out_wlast   = in_wlast;
      out_bready  = reg_out_bready;
      in_bvalid   = reg_in_bvalid;
      in_bid      = reg_in_bid;
      in_bresp    = reg_in_bresp;
    end
    else begin
      in_awready  = out_awready;
      out_awvalid = in_awvalid;
      out_awid    = in_awid;
      out_awaddr  = in_awaddr;
      out_awlen   = in_awlen;
      out_awsize  = in_awsize;
      out_awburst = in_awburst;
      in_wready   = out_wready;
      out_wvalid  = in_wvalid;
      out_wdata   = in_wdata;
      out_wstrb   = in_wstrb;
      out_wlast   = in_wlast;
      out_bready  = in_bready;
      in_bvalid   = out_bvalid;
      in_bid      = out_bid;
      in_bresp    = out_bresp;
    end
  end

  // old code
  // assign in_arready = out_arready;
  // assign out_arvalid = in_arvalid;
  // assign out_arid = in_arid;
  // assign out_araddr = in_araddr;
  // assign out_arlen = in_arlen;
  // assign out_arsize = in_arsize;
  // assign out_arburst = in_arburst;
  // assign out_rready = in_rready;
  // assign in_rvalid = out_rvalid;
  // assign in_rid = out_rid;
  // assign in_rdata = out_rdata;
  // assign in_rresp = out_rresp;
  // assign in_rlast = out_rlast;
  // assign in_awready = out_awready;
  // assign out_awvalid = in_awvalid;
  // assign out_awid = in_awid;
  // assign out_awaddr = in_awaddr;
  // assign out_awlen = in_awlen;
  // assign out_awsize = in_awsize;
  // assign out_awburst = in_awburst;
  // assign in_wready = out_wready;
  // assign out_wvalid = in_wvalid;
  // assign out_wdata = in_wdata;
  // assign out_wstrb = in_wstrb;
  // assign out_wlast = in_wlast;
  // assign out_bready = in_bready;
  // assign in_bvalid = out_bvalid;
  // assign in_bid = out_bid;
  // assign in_bresp = out_bresp;

endmodule
